feat(sum-subop): Spark BIGINT sum AArch64 SVE on HashAgg#563
Open
LeiRui wants to merge 4 commits into
Open
Conversation
9cab253 to
d0e469f
Compare
6726afa to
ddddbd4
Compare
Add SumAggregateSparkInt64SubOp (adapter updateGroupsFromDecoded, SVE kernel sveHashAggBatchUpdateGroupSums), DecodedVector hashAgg* layout APIs, env kill switch BOLT_SPARK_SUM_INT64_USE_SUBOP, and unit tests (DuckDB parity, env-off parity, SubOp vs Base, nullable gate, null constant, hashAgg layout modes). Co-authored-by: Old-Li883 <lichenhao9@huawei.com> Co-authored-by: helloxteen <zhangxin440@h-partners.com>
- Drop numNulls_/mayHaveNulls outer gates; unify updateBatch for raw/intermediate - Remove mode2==2 early exit; use value[0] for constant mapping in SVE kernel - Add NonNullFlat/NonNullConst SubOp-vs-Base parity tests
- Cache sumInt64SubOpCanUseSveKernel() (HWCAP_SVE + svcntb()==32) on Linux aarch64 - Check canUseSveKernel before decode; fallback to SumAggregateBase without decode - Hoist mode2 constant (value[0]) across 32-row SVE blocks - Deduplicate tests via expectSparkSumInt64SubOpMatchesBase; add dictionary/nullable cases
…llback - Remove mayPushdown/numNulls_/isDecimal gates on LAZY decode hook path - Consolidate Base fallback via delegateToBase helper
Author
|
Force-pushed Rebase / history
Behavior summary (this PR)
PR descriptionUpdated to match the above (tests, disable env, benchmark notes). Please review the latest description. Testing
Sorry for the noisy diff from the rebase. Please re-review if a previous approval was tied to the pre-rebase diff. Happy to squash or adjust commits before merge if you prefer. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What problem does this PR solve?
Spark
sum(bigint)on HashAgg can spend significant time in per-group scalar updates. This PR addsSumAggregateSparkInt64SubOpas the default aggregate for non-decimalspark_sum(bigint) → biginton Linux aarch64 with 256-bit SVE (svcntb()==32), providing batch SVE updates when Spark overflow checking is enabled. The fast path supports flat, constant, and dictionary inputs. Other platforms or SVE widths use originalSumAggregateBase. Disable SubOp withBOLT_SPARK_SUM_INT64_USE_SUBOP=0.Issue Number: N/A
Type of Change
Description
What
SumAggregateSparkInt64SubOpfor Spark non-decimalsum(bigint) → bigint(default viaregisterSumunless env opts out).updateBatchforaddRawInput/addIntermediateResults.sumInt64SubOpCanUseSveKernel(): Linux aarch64 +HWCAP_SVE+svcntb() == 32(256-bit SVE only); evaluated before decode.updateGroupsFromDecoded→sveHashAggBatchUpdateGroupSums(SVE kernel, aarch64-only TU,-march=armv8-a+sve).sveAccumulateFlaggedRows: batch accumulation for flat, constant, and dictionary-encoded values.DecodedVector:hashAggNullsLayoutMode/hashAggIndicesLayoutMode(mode1/mode2 — orthogonal null vs value layout) +hashAggMutable*buffer accessors for batch glue.Disable SubOp (
BOLT_SPARK_SUM_INT64_USE_SUBOP)SumAggregateSparkInt64SubOp(default)0SumAggregate<int64_t, int64_t, int64_t>(SumAggregateBase)false,no, oroff(ASCII, case-insensitive)0Executor example (Gluten / Spark):
spark.executorEnv.BOLT_SPARK_SUM_INT64_USE_SUBOP=0.Registration
Runtime call chain (
updateBatch; raw / intermediate symmetric)mayPushdown &&top-level Lazy → Base (pushdown, same asSumAggregateBase::updateInternal)Overflow? No → BasesumInt64SubOpCanUseSveKernel()? No → Base (no decode; covers non-Linux aarch64, no SVE, or non-256-bit SVE VL)Dictionary(LazyVector)) → load via callable hook, returnupdateGroupsFromDecoded→sveHashAggBatchUpdateGroupSums, returnSVE kernel supports nullable and non-nullable input batches.
Flowchart (mermaid — click to expand)
flowchart TD A[updateBatch] --> B{top-level Lazy+ pushdown?} B -->|yes| C[SumAggregateBase] B -->|no| D{Overflow?} D -->|no| C D -->|yes| E{canUseSveKernel?} E -->|no| C E -->|yes| F[decode] F --> G{indirect lazy?base == LAZY} G -->|yes| H[SimpleCallableHook+ LazyVector::load] G -->|no| I[updateGroupsFromDecoded→ Sve.cpp]Behavior summary
SumAggregateSparkInt64SubOpfor non-decimalspark_sum(bigint)unless env opts outsvcntb()==32)Dictionary(Lazy)) supported after decodeSumAggregateBasewithout decodeSource files and SVE path
File roles — register + SubOp + Base (mermaid — click to expand)
Legend: green = new (
SubOp.h/SubOp.cpp/Sve.cpp); yellow = modified (SumAggregate.cpp,DecodedVector); gray = existing (SumAggregateBase,SimpleCallableHook).After decode (two branches from
DecodedVector):SimpleCallableHook+LazyVector::load(scalar; not Base, not SVE)updateGroupsFromDecoded→Sve.cppPre-decode fallback (steps 1–2, or
!canUseSveKernel):delegateToBase()→SumAggregateBase(no decode).SumAggregate.cppregisterSumfactory: env check →SumAggregateSparkInt64SubOporSumAggregateBasefor Spark non-decimalBIGINT.SumAggregateSparkInt64SubOp.haddRawInput/addIntermediateResults→updateBatch; declaressumInt64SubOpCanUseSveKernel()andupdateGroupsFromDecoded(all platforms).SumAggregateSparkInt64SubOp.cppupdateBatchdispatch (raw / intermediate symmetric viadelegateToBase()): top-level lazy or!Overflowor!canUseSveKernel→ Base without decode; else decode → indirect lazy hook orupdateGroupsFromDecoded. Non-aarch64 stub for glue returnsfalse.DecodedVectorhashAggNullsLayoutMode(mode1 — input null layout) +hashAggIndicesLayoutMode(mode2 — value indexing); tags are orthogonal.hashAggMutable*→nulls_/data_/indices_. SVE glue reads mode1 for per-lane null mask, mode2 for value lookup. See mode table below.updateGroupsFromDecodedSumAggregateSparkInt64SubOpSve.cppon aarch64. ReadshashAgg*+ row mask; callssveHashAggBatchUpdateGroupSums. Returnstruewhen the SVE kernel handled the batch.sveHashAggBatchUpdateGroupSums·sveAccumulateFlaggedRows-march=armv8-a+sve); requires 256-bit SVE at runtime.DecodedVector mode1 / mode2 (click to expand)
mode1 and mode2 are independent — not a single flat/const/dict enum.
hashAggNullsLayoutMode— how to read input nullshashAggIndicesLayoutMode— how to read valuesnulls_[row]) · 2 = constant null (nulls_[0]) · 3 = via indices (nulls_[indices[row]])value[row]) · 2 = constant (value[0]) · 3 = dictionary (value[indices[row]])Typical shapes (
DecodedVectorTest.hashAggLayoutModes):Buffer accessors (what SVE glue reads after decode):
hashAggMutableCombinedNullBits()nulls_bit-packed null bitmap (may be nullptr)nulls_[row],nulls_[0], ornulls_[indices[row]]; mode1==0 → no bitmaphashAggMutableRawData()data_— scalar int64 payload (value[])value[row],value[0], orvalue[indices[row]]hashAggMutableIndices()indices_Tests (11 cases added for this PR; names below are gtest suffixes)
bolt_functions_spark_aggregates_testsumInt64SubOpParity,sumInt64SubOpEnvOffParity,sumInt64SubOpNullableSveGateexpectSparkSumInt64SubOpMatchesBase):SveMatchesBase,NullConstMatchesBase,NonNullFlatMatchesBase,NonNullConstMatchesBase,DictionaryMatchesBase,NumNullsZeroNullableInputMatchesBase,IntermediateDictionaryMatchesBasebolt_vector_testDecodedVectorTest.hashAggLayoutModesOther
SumAggregationTestcases in the same binary (e.g.overflow,decimalSum) are pre-existing, not part of this PR.Pass
--gtest_filterafter--so ctest runs only SubOp-related cases (without it, the aggregate binary runs all registered tests).Note:
sumInt64SubOpEnvOffParityskips on Windows (POSIXsetenv). SVE instruction coverage requires Linux aarch64 with 256-bit SVE (svcntb()==32); otherwise SubOp falls back to Base and tests still pass.Performance Impact
store_sales(group-by + multiplesum(bigint)/ decimal-unscaled sums;qtest×5) — ~6.3% mean E2E wall-time vs baseline where sum updates dominate.Click to view Benchmark Results
TPC-DS 1T (scale 1000) — full suite (self-tested): Numeric results match baseline on native Gluten bundle. End-to-end performance improvement across the full benchmark is not significant — int64
sum/SumAggregateSparkInt64SubOpwork is a small fraction of total query time on typical TPC-DS queries. Numbers below are a HashAgg-heavy micro-query only (qtest), to demonstrate the optimized path where sum updates dominate.Environment (micro-benchmark)
tpcds_bin_partitioned_varchar_parquet_1000)SumAggregateSparkInt64SubOpdefault)BOLT_SPARK_SUM_INT64_USE_SUBOP=1(baseline JAR has no SubOp; PR SubOp is also default-on when unset)Workload (same SQL run 5 times —
qtest…qtest-5— for stability):Note:
sum(cast(ss_quantity AS bigint))is an explicit bigint sum. Othersum(decimal(7,2))columns (ss_ext_sales_price, …) are rewritten by SparkDecimalAggregatestoMakeDecimal(sum(UnscaledValue(col)), …)— HashAgg still runssum(bigint), so they use the sameSumAggregateSparkInt64SubOp/SumAggregateBasepath (notDecimalSumAggregatewhenp+10≤18). E2E gain in this micro-query reflects all int64 sum updates in the HashAgg, notsum_qtyalone.Correctness (micro-query): Query output matches baseline on all 5 runs (
qtest…qtest-5; sorted diff, row-order agnostic).End-to-end wall time (micro-query; spark-sql seconds; lower is better):
Flame-graph attribution (async-profiler E2E; matched % of samples):
SumAggregateBaseaddRawInput / addIntermediateResults / updateInternalSumAggregateSparkInt64SubOp/ SVE kernel (sveHashAgg*,updateGroupsFromDecoded)Fireflames see fireflame.zip.
PR replaces per-group scalar
SumAggregateBasesum(bigint) updates with the SVE batch path; E2E time drops ~6% on this HashAgg-heavy micro-query.Release Note
Release Note:
Checklist (For Author)
qtestrun 5× with same check (5/5) + perf/flame in Benchmark Results.Breaking Changes